// $Id: track.c 110 2011-11-05 16:09:49Z cedric.shih@gmail.com $
/*
 * Copyright 2011 Cedric Shih (cedric dot shih at gmail dot com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include <zcbuffer.h>

#include "log.h"
#include "media.h"

#include "track.h"

void niortsp_track_free(struct niortsp_track *track)
{
	int rc;
	struct niortsp_listener *listener;

	if (!track) {
		return;
	}

	if (track->init && (rc = niortsp_track_teardown(track))) {
		niortsp_error("failed to tear down: %s", strerror(rc));
	}

	if (track->finalize) {
		niortsp_debug("invoking %p to finalize: %s/%s",
				track->finalize,
				track->media->name, track->name);
		(*track->finalize)(track, track->cbarg);
	}

	TAILQ_FOREACH(listener, &track->listeners, entry) {
		niortsp_warn("dirty listener: %p", listener->cb);
	}

	niortsp_listener_list_clear(&track->listeners);

	if (track->name) {
		free(track->name);
	}

	free(track);
}

int niortsp_track_describe(struct niortsp_track *track, const char *uri,
		struct zcbuffer *buffer)
{
	int rc;

	niortsp_debug("invoking %p to describe track: %s/%s", track->describe,
			track->media->name, track->name);

	if ((rc = (*track->describe)(track, uri, track->name, buffer,
			track->cbarg))) {
		niortsp_error("failed to describe track '%s/%s': %s",
				track->media->name, track->name,
				strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niortsp_track_setup(struct niortsp_track *track)
{
	int rc;

	niortsp_debug("invoking %p to setup track: %s/%s", track->setup,
			track->media->name, track->name);

	if ((rc = (*track->setup)(track, track->cbarg))) {
		niortsp_error("failed to setup track '%s/%s': %s",
				track->media->name, track->name,
				strerror(rc));
		goto finally;
	}

	track->init = 1;
	rc = 0;

finally:
	return rc;
}

int niortsp_track_teardown(struct niortsp_track *track)
{
	int rc;

	niortsp_debug("invoking %p to tear down track: %s/%s",
			track->teardown,
			track->media->name, track->name);

	if ((rc = (*track->teardown)(track, track->cbarg))) {
		niortsp_error("failed to tear down track '%s/%s': %s",
				track->media->name, track->name,
				strerror(rc));
		goto finally;
	}

	track->init = 0;
	rc = 0;

finally:
	return rc;
}

int niortsp_track_refresh(struct niortsp_track *track)
{
	int rc;

	if (!track->refresh) {
		rc = 0;
		goto finally;
	}

	niortsp_debug("invoking %p to refresh track: %s/%s",
			track->refresh,
			track->media->name, track->name);

	if ((rc = (*track->refresh)(track, track->cbarg))) {
		niortsp_error("failed to refresh '%s/%s': %s",
				track->media->name, track->name,
				strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niortsp_track_listen(struct niortsp_track *track,
		niortsp_data_cb cb, void *cbarg)
{
	int rc;

	if ((rc = niortsp_listener_list_add(&track->listeners, cb, cbarg))) {
		niortsp_error("failed to add: %s", strerror(rc));
		goto finally;
	}

	if (!track->init && track->setup && (rc = niortsp_track_setup(track))) {
		niortsp_error("failed to setup: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niortsp_track_unlisten(struct niortsp_track *track,
		niortsp_data_cb cb, void *cbarg)
{
	int rc;
	struct niortsp_listener *listener, *match;

	if (!track || !cb) {
		niortsp_error("null pointer(s)");
		rc = EINVAL;
		goto finally;
	}

	match = NULL;

	TAILQ_FOREACH(listener, &track->listeners, entry) {
		if (cbarg && cbarg != listener->cbarg) {
			continue;
		}

		if (listener->cb != cb) {
			continue;
		}

		match = listener;
		break;
	}

	if (!match) {
		rc = ENOENT;
		goto finally;
	}

	niortsp_listener_list_del(&track->listeners, match);

	if (track->init && TAILQ_EMPTY(&track->listeners) && track->teardown &&
			(rc = niortsp_track_teardown(track))) {
		niortsp_error("failed to tear down: %s", strerror(rc));
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int niortsp_track_push(struct niortsp_track *track, struct timeval *timestamp,
		struct zcbuffer *buffer)
{
	int rc;
	struct niortsp_listener *listener;

	if (!track || !timestamp|| !buffer) {
		niortsp_error("null pointer(s)");
		rc = EINVAL;
		goto finally;
	}

	niortsp_trace("pushing %d bytes to track %s/%s",
			zcbuffer_remaining(buffer), track->media->name,
			track->name);

	TAILQ_FOREACH(listener, &track->listeners, entry) {
		niortsp_trace("invoking %p for data", listener->cb);

		(*listener->cb)(timestamp, buffer, listener->cbarg);
	}

	rc = 0;

finally:
	return rc;
}

int niortsp_track_list_add(struct niortsp_track_list *list, const char *name,
		unsigned char payload_type, struct niortsp_track **ptr)
{
	int rc;
	struct niortsp_track *track;

	niortsp_debug("adding to list %p: %s", list, name);

	if (!(track = calloc(1, sizeof(*track)))) {
		rc = errno ? errno : -1;
		niortsp_error("calloc: %s", strerror(rc));
		goto finally;
	}

	TAILQ_INIT(&track->listeners);

	if (!(track->name = strdup(name))) {
		rc = errno ? errno : -1;
		niortsp_error("strdup: %s", strerror(rc));
		goto finally;
	}

	track->payload_type = payload_type;
	TAILQ_INSERT_TAIL(list, track, entry);
	*ptr = track;
	rc = 0;

finally:
	if (rc != 0) {
		if (track) {
			niortsp_track_free(track);
		}
	}
	return rc;
}

void niortsp_track_list_del(struct niortsp_track_list *list,
		struct niortsp_track *track)
{
	niortsp_debug("deleting from list %p: %s", list, track->name);

	TAILQ_REMOVE(list, track, entry);
	niortsp_track_free(track);
}

struct niortsp_track *niortsp_track_list_find(struct niortsp_track_list *list,
		const char *name)
{
	struct niortsp_track *track, *match = NULL;

	niortsp_debug("finding in list %p: %s", list, name);

	TAILQ_FOREACH(track, list, entry) {
		if (!strcmp(name, track->name)) {
			niortsp_debug("found in list %p: %s => %p",
					list, name, track);
			match = track;
			break;
		}
	}

	return match;
}

void niortsp_track_list_clear(struct niortsp_track_list *list)
{
	struct niortsp_track *track;

	niortsp_debug("clearing list %p", list);

	while ((track = TAILQ_FIRST(list))) {
		niortsp_track_list_del(list, track);
	}
}
